﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Drawing.Design;
using Ebc.DesignerDsl.CustomCode;
using Microsoft.CSharp;
using System.CodeDom;
using Microsoft.VisualStudio.Modeling;
using Ebc.DesignerDsl.TypeBrowserIntegration;

namespace Ebc.DesignerDsl
{
    public partial class ElementReferencesTargetElement
    {
        private ConnectionSelectionProperty output;
        private ConnectionSelectionProperty input;
        private TypeDefinition type;

        public TypeDefinition GetTypeValue()
        {
            if (this.type == null)
            {
                this.type = new TypeDefinition
                (
                    value => 
                    {
                        using (Transaction t = this.Store.TransactionManager.BeginTransaction("Change model"))
                        {
                            this.CSharpTypeString = value;
                            t.Commit();
                        }
                    }, 
                    value =>
                    {
                        using (Transaction t = this.Store.TransactionManager.BeginTransaction("Change model"))
                        {
                            this.ClrTypeString = value;
                            t.Commit();
                        }
                    }, 
                    () => this.CSharpTypeString,
                    usingNamespace =>
                    {
                        if (!this.SourceElement.Board.Usings.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Contains(usingNamespace))
                        {
                            using (Transaction t = this.Store.TransactionManager.BeginTransaction("Change model"))
                            {
                                this.SourceElement.Board.Usings += (Environment.NewLine + usingNamespace);

                                while (this.SourceElement.Board.Usings.StartsWith("\n") || this.SourceElement.Board.Usings.StartsWith(" ") || this.SourceElement.Board.Usings.StartsWith("\r"))
                                {
                                    this.SourceElement.Board.Usings = this.SourceElement.Board.Usings.Substring(1);
                                }

                                t.Commit();
                            }
                        }
                    }
                );
            }

            return this.type;
        }

        public void SetTypeValue(TypeDefinition value)
        {
        }

        public string GetTypeNameValue()
        {
            return this.CSharpTypeString;
        }

        public ConnectionSelectionProperty GetInputValue()
        {
            if (this.input == null)
            {
                this.input = new ConnectionSelectionProperty
                (
                    context => this.GetInTypes(true, context),
                    context => this.GetInTypes(false, context),
                    (value, type) =>
                    {
                        using (Transaction t = this.Store.TransactionManager.BeginTransaction("Change model"))
                        {
                            this.InputString = value.Item1;
                            t.Commit();
                        }

                        if (type == SelectedItemType.UsedOtherType)
                        {
                            this.type.SetType(value.Item2 != null ? value.Item2 : null);
                        }
                    },
                    () => this.InputString
                );
            }

            return this.input;
        }

        public void SetInputValue(ConnectionSelectionProperty value)
        {
        }

        public ConnectionSelectionProperty GetOutputValue()
        {
            if (this.output == null)
            {
                this.output = new ConnectionSelectionProperty
                (
                    context => this.GetOutTypes(true, context),
                    context => this.GetOutTypes(false, context),
                    (value, type) =>
                    {
                        using (Transaction t = this.Store.TransactionManager.BeginTransaction("Change model"))
                        {
                            this.OutputString = value.Item1;
                            t.Commit();
                        }

                        if (type == SelectedItemType.UsedOtherType)
                        {
                            this.type.SetType(value.Item2 != null ? value.Item2 : null);
                        }
                    },
                    () => this.OutputString
                );
            }

            return this.output;
        }

        public void SetOutputValue(ConnectionSelectionProperty value)
        {
        }

        public string GetInputNameValue()
        {
            return this.InputString;
        }

        public string GetOutputNameValue()
        {
            return this.OutputString;
        }

        private IEnumerable<Tuple<string, Type>> GetOutTypes(bool selected, ITypeDescriptorContext context)
        {
            var result = new List<Tuple<string, Type>>();

            if (this.SourceElement is ExistingPart)
            {
                var events = System.Type.GetType((this.SourceElement as ExistingPart).ClrTypeString).GetEvents();

                foreach (var item in events)
                {
                    var parameters = item.EventHandlerType.GetMethod("Invoke").GetParameters();
                    if ((parameters.Count() == 0 && string.IsNullOrEmpty(this.ClrTypeString)) ||
                        (parameters.Count() == 1 && this.ClrTypeString == parameters.First().ParameterType.AssemblyQualifiedName))
                    {
                        if (selected)
                        {
                            result.Add(new Tuple<string, Type>(item.Name, parameters.Count() > 0 ? parameters.First().ParameterType : null));
                        }
                    }
                    else if (!selected && parameters.Count() <= 1)
                    {
                        result.Add(new Tuple<string, Type>(item.Name, parameters.Count() > 0 ? parameters.First().ParameterType : null));
                    }
                }
            }

            return result;
        }

        private IEnumerable<Tuple<string, Type>> GetInTypes(bool selected, ITypeDescriptorContext context)
        {
            var result = new List<Tuple<string, Type>>();

            if (this.TargetElement is ExistingPart)
            {
                var partType = System.Type.GetType((this.TargetElement as ExistingPart).ClrTypeString);
                var events = partType.GetEvents();

                foreach (var item in partType.GetMethods().Where(el => el.IsPublic && !el.IsConstructor 
                    && !events.Any(ev => ev.GetAddMethod() == el || ev.GetRemoveMethod() == el)))
                {
                    var parameters = item.GetParameters();
                    if ((parameters.Count() == 0 && string.IsNullOrEmpty(this.ClrTypeString)) ||
                        (parameters.Count() == 1 && this.ClrTypeString == parameters.First().ParameterType.AssemblyQualifiedName))
                    {
                        if (selected)
                        {
                            result.Add(new Tuple<string, Type>(item.Name, parameters.Count() > 0 ? parameters.First().ParameterType : null));
                        }
                    }
                    else if (!selected && parameters.Count() <= 1)
                    {
                        result.Add(new Tuple<string, Type>(item.Name, parameters.Count() > 0 ? parameters.First().ParameterType : null));
                    }
                }
            }

            return result;
        }
    }
}